/** @file
 *
 *  Copyright (c) 2024, Mario Bălănică <mariobalanica02@gmail.com>
 *  Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd.
 *
 *  SPDX-License-Identifier: BSD-2-Clause-Patent
 *
 **/

#include <Library/CruLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/TimerLib.h>
#include <Library/IoLib.h>

/** @addtogroup RK_HAL_Driver
 *  @{
 */

/** @addtogroup CRU
 *  @{
 */

/** @defgroup CRU_How_To_Use How To Use
 *  @{

 The CRU driver can be used as follows:

 - Invoke cru functions to set clk rate, enable or disable clk, reset clk in each device.
 - The gate and soft reset id is include register offset and shift information:

       con_offset: id /16
       shift: id %16

 - The mux and div id is include register offset, shift, mask information:

       [15:0]: con
       [23:16]: shift
       [31:24]: width

 - CRU driver is just responsible for passing simple command data, And
 the usecount is the user's responsibility. Protection the usecount at the driver layer.
 - More details refer to APIs' descriptions as below.

 @} */

/** @defgroup CRU_Private_Definition Private Definition
 *  @{
 */
/********************* Private MACRO Definition ******************************/
#define PWRDOWN_SHIT        13
#define PWRDOWN_MASK        1 << PWRDOWN_SHIT
#define PLL_POSTDIV1_SHIFT  12
#define PLL_POSTDIV1_MASK   0x7 << PLL_POSTDIV1_SHIFT
#define PLL_FBDIV_SHIFT     0
#define PLL_FBDIV_MASK      0xfff << PLL_FBDIV_SHIFT
#define PLL_POSTDIV2_SHIFT  6
#define PLL_POSTDIV2_MASK   0x7 << PLL_POSTDIV2_SHIFT
#define PLL_REFDIV_SHIFT    0
#define PLL_REFDIV_MASK     0x3f << PLL_REFDIV_SHIFT
#define PLL_DSMPD_SHIFT     12
#define PLL_DSMPD_MASK      1 << PLL_DSMPD_SHIFT
#define PLL_FRAC_SHIFT      0
#define PLL_FRAC_MASK       0xffffff << PLL_FRAC_SHIFT

#define PLLCON0_M_SHIFT      0
#define PLLCON0_M_MASK       0x3ff << PLLCON0_M_SHIFT
#define PLLCON1_P_SHIFT      0
#define PLLCON1_P_MASK       0x3f << PLLCON1_P_SHIFT
#define PLLCON1_S_SHIFT      6
#define PLLCON1_S_MASK       0x7 << PLLCON1_S_SHIFT
#define PLLCON2_K_SHIFT      0
#define PLLCON2_K_MASK       0xffff << PLLCON2_K_SHIFT
#define PLLCON1_PWRDOWN      BIT(13)
#define PLLCON6_LOCK_STATUS  BIT(15)

#define MIN_FOUTVCO_FREQ  (800 * MHZ)
#define MAX_FOUTVCO_FREQ  (2000 * MHZ)
#define MIN_FOUT_FREQ     (24 * MHZ)
#define MAX_FOUT_FREQ     (1600 * MHZ)

#define RK3588_VCO_MIN_HZ   (2250UL * MHZ)
#define RK3588_VCO_MAX_HZ   (4500UL * MHZ)
#define RK3588_FOUT_MIN_HZ  (37UL * MHZ)
#define RK3588_FOUT_MAX_HZ  (4500UL * MHZ)

#define EXPONENT_OF_FRAC_PLL  24
#define RK_PLL_MODE_SLOW      0
#define RK_PLL_MODE_NORMAL    1
#define RK_PLL_MODE_DEEP      2
#define PLL_GET_PLLMODE(val, shift, mask)  (((uint32_t)(val) & mask) >> shift)

#define PLL_GET_FBDIV(x)  (((uint32_t)(x) & (PLL_FBDIV_MASK)) >> PLL_FBDIV_SHIFT)
#define PLL_GET_REFDIV(x) \
    (((uint32_t)(x) & (PLL_REFDIV_MASK)) >> PLL_REFDIV_SHIFT)
#define PLL_GET_POSTDIV1(x) \
    (((uint32_t)(x) & (PLL_POSTDIV1_MASK)) >> PLL_POSTDIV1_SHIFT)
#define PLL_GET_POSTDIV2(x) \
    (((uint32_t)(x) & (PLL_POSTDIV2_MASK)) >> PLL_POSTDIV2_SHIFT)
#define PLL_GET_DSMPD(x)  (((uint32_t)(x) & (PLL_DSMPD_MASK)) >> PLL_DSMPD_SHIFT)
#define PLL_GET_FRAC(x)   (((uint32_t)(x) & (PLL_FRAC_MASK)) >> PLL_FRAC_SHIFT)

#define CRU_PLL_ROUND_UP_TO_KHZ(x)  (HAL_DIV_ROUND_UP((x), KHZ) * KHZ)

/********************* Private Structure Definition **************************/
static struct PLL_CONFIG  g_rockchipAutoTable;

/********************* Private Variable Definition ***************************/

/********************* Private Function Definition ***************************/

/** Calculate the greatest common divisor */
static uint32_t
CRU_Gcd (
  uint32_t  m,
  uint32_t  n
  )
{
  int  t;

  while (m > 0) {
    if (n > m) {
      t = m;
      m = n;
      n = t;
    }

    m -= n;
  }

  return n;
}

/**
 * @brief Rockchip pll clk set postdiv.
 * @param  foutHz: output freq
 * @param  *postDiv1: pll postDiv1
 * @param  *postDiv2: pll postDiv2
 * @param  *foutVco: pll vco
 * @return HAL_Status.
 * How to calculate the PLL:
 *     Formulas also embedded within the fractional PLL Verilog model:
 *     If DSMPD = 1 (DSM is disabled, "integer mode")
 *     FOUTVCO = FREF / REFDIV * FBDIV
 *     FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
 *     Where:
 *     FOUTVCO = fractional PLL non-divided output frequency
 *     FOUTPOSTDIV = fractional PLL divided output frequency
 *               (output of second post divider)
 */
static HAL_Status
CRU_PllSetPostDiv (
  uint32_t  foutHz,
  uint32_t  *postDiv1,
  uint32_t  *postDiv2,
  uint32_t  *foutVco
  )
{
  uint32_t  freq;

  if (foutHz < MIN_FOUTVCO_FREQ) {
    for (*postDiv1 = 1; *postDiv1 <= 7; (*postDiv1)++) {
      for (*postDiv2 = 1; *postDiv2 <= 7; (*postDiv2)++) {
        freq = foutHz * (*postDiv1) * (*postDiv2);
        if ((freq >= MIN_FOUTVCO_FREQ) && (freq <= MAX_FOUTVCO_FREQ)) {
          *foutVco = freq;

          return HAL_OK;
        }
      }
    }

    return HAL_ERROR;
  } else {
    *postDiv1 = 1;
    *postDiv2 = 1;

    return HAL_OK;
  }
}

/**
 * @brief Get pll parameter by auto.
 * @param  finHz: pll intput freq
 * @param  foutHz: pll output freq
 * @return struct PLL_CONFIG.
 * How to calculate the PLL:
 *     Formulas also embedded within the fractional PLL Verilog model:
 *     If DSMPD = 1 (DSM is disabled, "integer mode")
 *     FOUTVCO = FREF / REFDIV * FBDIV
 *     FOUTPOSTDIV = FOUTVCO / POSTDIV1 / POSTDIV2
 *     Where:
 *     FOUTVCO = fractional PLL non-divided output frequency
 *     FOUTPOSTDIV = fractional PLL divided output frequency
 *               (output of second post divider)
 *     FREF = fractional PLL input reference frequency, (the OSC_HZ 24MHz input)
 *     REFDIV = fractional PLL input reference clock divider
 *     FBDIV = Integer value programmed into feedback divide
 */
static const struct PLL_CONFIG *
CRU_PllSetByAuto (
  uint32_t  finHz,
  uint32_t  foutHz
  )
{
  struct PLL_CONFIG  *rateTable = &g_rockchipAutoTable;
  uint32_t           foutVco = foutHz;
  uint64_t           fin64, frac64;
  uint32_t           postDiv1, postDiv2;
  uint32_t           clkGcd = 0;
  HAL_Status         error;

  if ((finHz == 0) || (foutHz == 0) || (foutHz == finHz)) {
    return NULL;
  }

  error = CRU_PllSetPostDiv (foutHz, &postDiv1, &postDiv2, &foutVco);
  if (error) {
    return NULL;
  }

  rateTable->postDiv1 = postDiv1;
  rateTable->postDiv2 = postDiv2;
  rateTable->dsmpd    = 1;

  if ((finHz / MHZ * MHZ == finHz) && (foutHz / MHZ * MHZ == foutHz)) {
    finHz            /= MHZ;
    foutVco          /= MHZ;
    clkGcd            = CRU_Gcd (finHz, foutVco);
    rateTable->refDiv = finHz / clkGcd;
    rateTable->fbDiv  = foutVco / clkGcd;

    rateTable->frac = 0;
  } else if (finHz / MHZ * MHZ != finHz) {
    clkGcd              = foutHz / finHz;
    rateTable->fbDiv    = clkGcd;
    rateTable->refDiv   = 1;
    rateTable->postDiv1 = 1;
    rateTable->postDiv2 = 1;

    fin64           = foutHz * rateTable->refDiv;
    fin64           = HAL_DivU64 (fin64 << EXPONENT_OF_FRAC_PLL, finHz);
    frac64          = rateTable->fbDiv;
    frac64          = rateTable->fbDiv << EXPONENT_OF_FRAC_PLL;
    frac64          = fin64 - frac64;
    rateTable->frac = frac64;
    if (rateTable->frac > 0) {
      rateTable->dsmpd = 0;
    }
  } else {
    clkGcd            = CRU_Gcd (finHz / MHZ, foutVco / MHZ);
    rateTable->refDiv = finHz / MHZ / clkGcd;
    rateTable->fbDiv  = foutVco / MHZ / clkGcd;
    rateTable->frac   = 0;

    frac64          = (foutVco % MHZ);
    fin64           = finHz;
    frac64          = frac64 * rateTable->refDiv;
    frac64          = HAL_DivU64 (frac64 << EXPONENT_OF_FRAC_PLL, fin64);
    rateTable->frac = frac64;
    if (rateTable->frac > 0) {
      rateTable->dsmpd = 0;
    }
  }

  return rateTable;
}

/**
 * @brief Get pll parameter by rateTable.
 * @param  *pSetup: struct PLL_SETUP struct, Contains PLL register parameters
 * @param  rate: pll target rate.
 * @return struct PLL_CONFIG.
 * How to calculate the PLL:
 *     Look up the rateTable to get the PLL config parameter
 */
static const struct PLL_CONFIG *
CRU_PllGetSettings (
  struct PLL_SETUP  *pSetup,
  uint32_t          rate
  )
{
  const struct PLL_CONFIG  *rateTable = pSetup->rateTable;

  if (rateTable == NULL) {
    return CRU_PllSetByAuto (PLL_INPUT_OSC_RATE, rate);
  }

  while (rateTable->rate) {
    if (rateTable->rate == rate) {
      break;
    }

    rateTable++;
  }

  if (rateTable->rate != rate) {
    return CRU_PllSetByAuto (PLL_INPUT_OSC_RATE, rate);
  } else {
    return rateTable;
  }
}

static const struct PLL_CONFIG *
CRU_PllV1SetByAuto (
  uint32_t  fin_hz,
  uint32_t  fout_hz
  )
{
  struct PLL_CONFIG  *rate_table = &g_rockchipAutoTable;
  uint32_t           p, m, s;
  uint64_t           fvco, fref, fout, ffrac;

  if ((fin_hz == 0) || (fout_hz == 0) || (fout_hz == fin_hz)) {
    return NULL;
  }

  if ((fout_hz > RK3588_FOUT_MAX_HZ) || (fout_hz < RK3588_FOUT_MIN_HZ)) {
    return NULL;
  }

  for (s = 0; s <= 6; s++) {
    fvco = fout_hz << s;
    if ((fvco < RK3588_VCO_MIN_HZ) ||
        (fvco > RK3588_VCO_MAX_HZ))
    {
      continue;
    }

    for (p = 1; p <= 4; p++) {
      for (m = 64; m <= 1023; m++) {
        if ((fvco >= m * fin_hz / p) && (fvco < (m + 1) * fin_hz / p)) {
          rate_table->p = p;
          rate_table->m = m;
          rate_table->s = s;
          fref          = fin_hz / p;
          ffrac         = fvco - (m * fref);
          fout          = ffrac * 65536;
          rate_table->k = fout / fref;
          return rate_table;
        }
      }
    }
  }

  DEBUG ((DEBUG_ERROR, "%a: CANNOT FIND Fout by auto,fout = %lu\n", __func__, fout_hz));
  return NULL;
}

/**
 * @brief Get pll parameter by rateTable. (RK3588)
 * @param  *pSetup: struct PLL_SETUP struct, Contains PLL register parameters
 * @param  rate: pll target rate.
 * @return struct PLL_CONFIG.
 * How to calculate the PLL:
 *     Look up the rateTable to get the PLL config parameter
 */
static const struct PLL_CONFIG *
CRU_PllV1GetSettings (
  struct PLL_SETUP  *pSetup,
  uint32_t          rate
  )
{
  const struct PLL_CONFIG  *rateTable = pSetup->rateTable;

  if (rateTable == NULL) {
    return CRU_PllV1SetByAuto (PLL_INPUT_OSC_RATE, rate);
  }

  while (rateTable->rate) {
    if (rateTable->rate == rate) {
      break;
    }

    rateTable++;
  }

  if (rateTable->rate != rate) {
    return CRU_PllV1SetByAuto (PLL_INPUT_OSC_RATE, rate);
  } else {
    return rateTable;
  }
}

/** @} */
/********************* Public Function Definition ****************************/

/** @defgroup CRU_Exported_Functions_Group5 Other Functions
 *  @attention these APIs allow direct use in the HAL layer.
 *  @{
 */

/**
 * @brief Get pll freq.
 * @param  *pSetup: struct PLL_SETUP struct,Contains PLL register parameters
 * @return pll rate.
 * How to calculate the PLL:
 *     Formulas also embedded within the fractional PLL Verilog model:
 *     If DSMPD = 1 (DSM is disabled, "integer mode")
 *     FOUTVCO = FREF / REFDIV * FBDIV
 *     FOUT = FOUTVCO / POSTDIV1 / POSTDIV2
 *     If DSMPD = 0 (DSM is enabled, "fractional mode")
 *     FOUTVCO = (FREF / REFDIV) * (FBDIV + FRAC / (2^24))
 *     FOUTPOSTDIV = FOUTVCO / (POSTDIV1*POSTDIV2)
 *     FOUT = FOUTVCO / POSTDIV1 / POSTDIV2
 */
uint32_t
HAL_CRU_GetPllFreq (
  struct PLL_SETUP  *pSetup
  )
{
  uint32_t  refDiv, fbDiv, postdDv1, postDiv2, frac, dsmpd;
  uint32_t  mode = 0, rate = PLL_INPUT_OSC_RATE;

  mode = PLL_GET_PLLMODE (
           READ_REG (*(pSetup->modeOffset)),
           pSetup->modeShift,
           pSetup->modeMask
           );

  switch (mode) {
    case RK_PLL_MODE_SLOW:
      rate = PLL_INPUT_OSC_RATE;
      break;
    case RK_PLL_MODE_NORMAL:
      postdDv1 = PLL_GET_POSTDIV1 (READ_REG (*(pSetup->conOffset0)));
      fbDiv    = PLL_GET_FBDIV (READ_REG (*(pSetup->conOffset0)));
      postDiv2 = PLL_GET_POSTDIV2 (READ_REG (*(pSetup->conOffset1)));
      refDiv   = PLL_GET_REFDIV (READ_REG (*(pSetup->conOffset1)));
      dsmpd    = PLL_GET_DSMPD (READ_REG (*(pSetup->conOffset1)));
      frac     = PLL_GET_FRAC (READ_REG (*(pSetup->conOffset2)));
      rate     = (rate / refDiv) * fbDiv;
      if (dsmpd == 0) {
        uint64_t  fracRate = PLL_INPUT_OSC_RATE;

        fracRate *= frac;
        fracRate  = fracRate >> EXPONENT_OF_FRAC_PLL;
        fracRate  = fracRate / refDiv;
        rate     += fracRate;
      }

      rate = rate / (postdDv1 * postDiv2);
      rate = CRU_PLL_ROUND_UP_TO_KHZ (rate);
      break;
    case RK_PLL_MODE_DEEP:
    default:
      rate = 32768;
      break;
  }

  return rate;
}

/**
 * @brief Set pll freq.
 * @param  *pSetup: struct PLL_SETUP struct,Contains PLL register parameters
 * @param  rate: pll set rate
 * @return HAL_Status.
 * How to calculate the PLL:
 *     Force PLL into slow mode
 *     Pll Power down
 *     Pll Config fbDiv, refDiv, postdDv1, postDiv2, dsmpd, frac
 *     Pll Power up
 *     Waiting for pll lock
 *     Force PLL into normal mode
 */
HAL_Status
HAL_CRU_SetPllFreq (
  struct PLL_SETUP  *pSetup,
  uint32_t          rate
  )
{
  const struct PLL_CONFIG  *pConfig;
  int                      delay = 2400;

  if (rate == HAL_CRU_GetPllFreq (pSetup)) {
    return HAL_OK;
  } else if (rate < MIN_FOUT_FREQ) {
    return HAL_INVAL;
  } else if (rate > MAX_FOUT_FREQ) {
    return HAL_INVAL;
  }

  pConfig = CRU_PllGetSettings (pSetup, rate);
  if (!pConfig) {
    return HAL_ERROR;
  }

  /* Force PLL into slow mode to ensure output stable clock */
  WRITE_REG_MASK_WE (*(pSetup->modeOffset), pSetup->modeMask, RK_PLL_MODE_SLOW << pSetup->modeShift);

  /* Pll Power down */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PWRDOWN_MASK, 1 << PWRDOWN_SHIT);

  /* Pll Config */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PLL_POSTDIV2_MASK, pConfig->postDiv2 << PLL_POSTDIV2_SHIFT);
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PLL_REFDIV_MASK, pConfig->refDiv << PLL_REFDIV_SHIFT);
  WRITE_REG_MASK_WE (*(pSetup->conOffset0), PLL_POSTDIV1_MASK, pConfig->postDiv1 << PLL_POSTDIV1_SHIFT);
  WRITE_REG_MASK_WE (*(pSetup->conOffset0), PLL_FBDIV_MASK, pConfig->fbDiv << PLL_FBDIV_SHIFT);
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PLL_DSMPD_MASK, pConfig->dsmpd << PLL_DSMPD_SHIFT);

  if (pConfig->frac) {
    WRITE_REG (*(pSetup->conOffset2), (READ_REG (*(pSetup->conOffset2)) & 0xff000000) | pConfig->frac);
  }

  /* Pll Power up */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PWRDOWN_MASK, 0 << PWRDOWN_SHIT);

  /* Waiting for pll lock */
  while (delay > 0) {
    if (pSetup->stat0) {
      if (READ_REG (*(pSetup->stat0)) & (1 << pSetup->lockShift)) {
        break;
      }
    } else {
      if (READ_REG (*(pSetup->conOffset1)) & (1 << pSetup->lockShift)) {
        break;
      }
    }

    HAL_CPUDelayUs (1000);
    delay--;
  }

  if (delay == 0) {
    return HAL_TIMEOUT;
  }

  /* Force PLL into normal mode */
  WRITE_REG_MASK_WE (*(pSetup->modeOffset), pSetup->modeMask, RK_PLL_MODE_NORMAL << pSetup->modeShift);

  return HAL_OK;
}

/**
 * @brief Get pll v1 freq(RK3588).
 * @param  *pSetup: struct PLL_SETUP struct,Contains PLL register parameters
 * @return pll rate.
 * How to calculate the PLL:
 *     Formulas also embedded within the fractional PLL Verilog model:
 *     If K = 0 (DSM is disabled, "integer mode")
 *     FOUTVCO = FREF / P * M
 *     FOUT = FOUTVCO / 2^S
 *     If K > 0 (DSM is enabled, "fractional mode")
 *     FOUTVCO = (FREF / P) * (M + K / 65536)
 *     FOUT = FOUTVCO / 2^S
 */
uint32_t
HAL_CRU_GetPllV1Freq (
  struct PLL_SETUP  *pSetup
  )
{
  uint32_t  m, p, s, k;
  uint64_t  rate = PLL_INPUT_OSC_RATE;
  uint32_t  mode = 0;

  if (pSetup->modeMask) {
    mode = PLL_GET_PLLMODE (
             READ_REG (*(pSetup->modeOffset)),
             pSetup->modeShift,
             pSetup->modeMask
             );
  } else {
    mode = RK_PLL_MODE_NORMAL;
  }

  switch (mode) {
    case RK_PLL_MODE_SLOW:
      rate = PLL_INPUT_OSC_RATE;
      break;
    case RK_PLL_MODE_NORMAL:
      m = (READ_REG (*(pSetup->conOffset0)) & PLLCON0_M_MASK) >> PLLCON0_M_SHIFT;
      p = (READ_REG (*(pSetup->conOffset1)) & PLLCON1_P_MASK) >> PLLCON1_P_SHIFT;
      s = (READ_REG (*(pSetup->conOffset1)) & PLLCON1_S_MASK) >> PLLCON1_S_SHIFT;
      k = (READ_REG (*(pSetup->conOffset2)) & PLLCON2_K_MASK) >> PLLCON2_K_SHIFT;

      rate *= m;
      rate  = rate / p;
      if (k) {
        /* fractional mode */
        uint64_t  frac = PLL_INPUT_OSC_RATE / p;

        frac *= k;
        frac  = frac / 65536;
        rate += frac;
      }

      rate = rate >> s;

      break;
    case RK_PLL_MODE_DEEP:
    default:
      rate = 32768;
      break;
  }

  return rate;
}

/**
 * @brief Set pll v1 freq(RK3588).
 * @param  *pSetup: struct PLL_SETUP struct,Contains PLL register parameters
 * @param  rate: pll set rate
 * @return HAL_Status.
 * How to calculate the PLL:
 *     Force PLL into slow mode
 *     Pll Power down
 *     Pll Config M, P, S, K
 *     Pll Power up
 *     Waiting for pll lock
 *     Force PLL into normal mode
 */
HAL_Status
HAL_CRU_SetPllV1Freq (
  struct PLL_SETUP  *pSetup,
  uint32_t          rate
  )
{
  const struct PLL_CONFIG  *pConfig;
  int                      delay = 24000000;

  if (rate == HAL_CRU_GetPllV1Freq (pSetup)) {
    return HAL_OK;
  } else if (rate < RK3588_FOUT_MIN_HZ) {
    return HAL_INVAL;
  } else if (rate > RK3588_FOUT_MAX_HZ) {
    return HAL_INVAL;
  }

  pConfig = CRU_PllV1GetSettings (pSetup, rate);
  if (!pConfig) {
    return HAL_ERROR;
  }

  /* Force PLL into slow mode to ensure output stable clock */
  if (pSetup->modeMask) {
    WRITE_REG_MASK_WE (*(pSetup->modeOffset), pSetup->modeMask, RK_PLL_MODE_SLOW << pSetup->modeShift);
  }

  /* Pll Power down */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PWRDOWN_MASK, 1 << PWRDOWN_SHIT);

  /* Pll Config */
  WRITE_REG_MASK_WE (*(pSetup->conOffset0), PLLCON0_M_MASK, pConfig->m << PLLCON0_M_SHIFT);
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PLLCON1_P_MASK, pConfig->p << PLLCON1_P_SHIFT);
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PLLCON1_S_MASK, pConfig->s << PLLCON1_S_SHIFT);
  if (pConfig->k) {
    WRITE_REG_MASK_WE (*(pSetup->conOffset2), PLLCON2_K_MASK, pConfig->k << PLLCON2_K_SHIFT);
  }

  /* Pll Power up */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PWRDOWN_MASK, 0 << PWRDOWN_SHIT);

  /* Waiting for pll lock */
  while (delay > 0) {
    if (READ_REG (*(pSetup->conOffset6)) & (1 << pSetup->lockShift)) {
      break;
    }

    delay--;
  }

  if (delay == 0) {
    return HAL_TIMEOUT;
  }

  /* Force PLL into normal mode */
  if (pSetup->modeMask) {
    WRITE_REG_MASK_WE (*(pSetup->modeOffset), pSetup->modeMask, RK_PLL_MODE_NORMAL << pSetup->modeShift);
  }

  return HAL_OK;
}

/**
 * @brief Set pll power up.
 * @param  *pSetup: struct PLL_SETUP struct,Contains PLL register parameters
 * @return HAL_Status.
 */
HAL_Status
HAL_CRU_SetPllPowerUp (
  struct PLL_SETUP  *pSetup
  )
{
  int  delay = 2400;

  /* Pll Power up */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PWRDOWN_MASK, 0 << PWRDOWN_SHIT);

  /* Waiting for pll lock */
  while (delay > 0) {
    if (pSetup->stat0) {
      if (READ_REG (*(pSetup->stat0)) & (1 << pSetup->lockShift)) {
        break;
      }
    } else {
      if (READ_REG (*(pSetup->conOffset1)) & (1 << pSetup->lockShift)) {
        break;
      }
    }

    HAL_CPUDelayUs (1000);
    delay--;
  }

  if (delay == 0) {
    return HAL_TIMEOUT;
  }

  return HAL_OK;
}

/**
 * @brief Set pll power down.
 * @param  *pSetup: struct PLL_SETUP struct,Contains PLL register parameters
 * @return HAL_Status.
 */
HAL_Status
HAL_CRU_SetPllPowerDown (
  struct PLL_SETUP  *pSetup
  )
{
  /* Pll Power down */
  WRITE_REG_MASK_WE (*(pSetup->conOffset1), PWRDOWN_MASK, 1 << PWRDOWN_SHIT);

  return HAL_OK;
}

/**
 * @brief  IP Clock is Enabled API
 * @param  clk: clk gate id
 * @return HAL_Check
 */
HAL_Check
HAL_CRU_ClkIsEnabled (
  uint32_t  clockId
  )
{
  CRU_CLOCK  *clk;
  uint32_t   index, shift, address;
  HAL_Check  ret;

  clk = HAL_CRU_ClkGetById (clockId);
  if (clk == NULL) {
    ASSERT (FALSE);
    return HAL_FALSE;
  } else if ((clk->flags & CLOCK_SUPPORT_GATE) == 0) {
    return HAL_TRUE;
  }

  index   = CLK_GATE_GET_REG_OFFSET (clk->gate);
  shift   = CLK_GATE_GET_BITS_SHIFT (clk->gate);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->gateOffset, index);

  ret = (HAL_Check)(!((MmioRead32 (address) & (1 << shift)) >> shift));

  return ret;
}

/**
 * @brief  IP Clock Enable API
 * @param  clk: clk gate id
 * @return NONE
 */
HAL_Status
HAL_CRU_ClkEnable (
  uint32_t  clockId
  )
{
  CRU_CLOCK  *clk;
  uint32_t   index, shift, address;

  clk = HAL_CRU_ClkGetById (clockId);
  if ((clk == NULL) || ((clk->flags & CLOCK_SUPPORT_GATE) == 0)) {
    ASSERT (FALSE);
    return HAL_ERROR;
  }

  index   = CLK_GATE_GET_REG_OFFSET (clk->gate);
  shift   = CLK_GATE_GET_BITS_SHIFT (clk->gate);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->gateOffset, index);

  MmioWrite32 (address, VAL_MASK_WE (1U << shift, 0U << shift));

  return HAL_OK;
}

/**
 * @brief  IP Clock Disabled API
 * @param  clk: clk gate id
 * @return NONE
 */
HAL_Status
HAL_CRU_ClkDisable (
  uint32_t  clockId
  )
{
  CRU_CLOCK  *clk;
  uint32_t   index, shift, address;

  clk = HAL_CRU_ClkGetById (clockId);
  if ((clk == NULL) || ((clk->flags & CLOCK_SUPPORT_GATE) == 0)) {
    ASSERT (FALSE);
    return HAL_ERROR;
  }

  index   = CLK_GATE_GET_REG_OFFSET (clk->gate);
  shift   = CLK_GATE_GET_BITS_SHIFT (clk->gate);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->gateOffset, index);

  MmioWrite32 (address, VAL_MASK_WE (1U << shift, 1U << shift));

  return HAL_OK;
}

/**
 * @brief  IP Clock set div API
 * @param  clockId: div id(Contains div offset, shift, mask information)
 * @param  divValue: div value
 * @return NONE
 */
HAL_Status
HAL_CRU_ClkSetDiv (
  uint32_t  clockId,
  uint32_t  divValue
  )
{
  CRU_CLOCK  *clk;
  uint32_t   shift, mask, index, address;

  clk = HAL_CRU_ClkGetById (clockId);
  if ((clk == NULL) || ((clk->flags & CLOCK_SUPPORT_DIV) == 0)) {
    ASSERT (FALSE);
    return HAL_ERROR;
  }

  index = CLK_DIV_GET_REG_OFFSET (clk->div);
  shift = CLK_DIV_GET_BITS_SHIFT (clk->div);
  HAL_ASSERT (shift < 16);
  mask    = CLK_DIV_GET_MASK (clk->div);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->divOffset, index);

  if (divValue > mask) {
    divValue = mask;
  }

  MmioWrite32 (address, VAL_MASK_WE (mask, (divValue - 1U) << shift));

  return HAL_OK;
}

/**
 * @brief  IP Clock get div API
 * @param  clockId: div id(Contains div offset, shift, mask information)
 * @return div value
 */
uint32_t
HAL_CRU_ClkGetDiv (
  uint32_t  clockId
  )
{
  CRU_CLOCK  *clk;
  uint32_t   shift, mask, index, divValue, address;

  clk = HAL_CRU_ClkGetById (clockId);
  if ((clk == NULL) || ((clk->flags & CLOCK_SUPPORT_DIV) == 0)) {
    ASSERT (clk != NULL);
    return 1;
  }

  index = CLK_DIV_GET_REG_OFFSET (clk->div);
  shift = CLK_DIV_GET_BITS_SHIFT (clk->div);
  HAL_ASSERT (shift < 16);
  mask    = CLK_DIV_GET_MASK (clk->div);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->divOffset, index);

  divValue = ((MmioRead32 (address) & mask) >> shift) + 1;

  return divValue;
}

/**
 * @brief  IP Clock set mux API
 * @param  clockId: mux id(Contains mux offset, shift, mask information)
 * @param  muxValue: mux value
 * @return NONE
 */
HAL_SECTION_SRAM_CODE
HAL_Status
HAL_CRU_ClkSetMux (
  uint32_t  clockId,
  uint32_t  muxValue
  )
{
  CRU_CLOCK  *clk;
  uint32_t   shift, mask, index, address;

  clk = HAL_CRU_ClkGetById (clockId);
  if ((clk == NULL) || ((clk->flags & CLOCK_SUPPORT_MUX) == 0)) {
    ASSERT (FALSE);
    return HAL_ERROR;
  }

  index = CLK_MUX_GET_REG_OFFSET (clk->mux);
  shift = CLK_MUX_GET_BITS_SHIFT (clk->mux);
  HAL_ASSERT (shift < 16);
  mask    = CLK_MUX_GET_MASK (clk->mux);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->muxOffset, index);

  MmioWrite32 (address, VAL_MASK_WE (mask, muxValue << shift));

  return HAL_OK;
}

/**
 * @brief  IP Clock get mux API
 * @param  clockId: mux id(Contains mux offset, shift, mask information)
 * @return mux value
 */
HAL_SECTION_SRAM_CODE
uint32_t
HAL_CRU_ClkGetMux (
  uint32_t  clockId
  )
{
  CRU_CLOCK  *clk;
  uint32_t   shift, mask, index, muxValue, address;

  clk = HAL_CRU_ClkGetById (clockId);
  if ((clk == NULL) || ((clk->flags & CLOCK_SUPPORT_MUX) == 0)) {
    ASSERT (FALSE);
    return 0;
  }

  index = CLK_MUX_GET_REG_OFFSET (clk->mux);
  shift = CLK_MUX_GET_BITS_SHIFT (clk->mux);
  HAL_ASSERT (shift < 16);
  mask    = CLK_MUX_GET_MASK (clk->mux);
  address = CRU_CON_REG_ADDRESS (clk->regBase, clk->muxOffset, index);

  muxValue = ((MmioRead32 (address) & mask) >> shift);

  return muxValue;
}

/**
 * @brief  Get frac div config.
 * @param  rateOut: clk out rate.
 * @param  rate: clk src rate.
 * @param  numerator: the returned numerator.
 * @param  denominator: the returned denominator.
 * @return HAL_Status.
 */
HAL_Status
HAL_CRU_FracdivGetConfig (
  uint32_t  rateOut,
  uint32_t  rate,
  uint32_t  *numerator,
  uint32_t  *denominator
  )
{
  uint32_t  gcdVal;

  gcdVal = CRU_Gcd (rate, rateOut);
  if (!gcdVal) {
    return HAL_ERROR;
  }

  *numerator   = rateOut / gcdVal;
  *denominator = rate / gcdVal;

  if (*numerator < 4) {
    *numerator   *= 4;
    *denominator *= 4;
  }

  if ((*numerator > 0xffff) || (*denominator > 0xffff)) {
    return HAL_INVAL;
  }

  return HAL_OK;
}

/**
 * @brief  Get Np5 best div.
 * @param  clockId: clk id.
 * @param  rate: clk rate.
 * @param  pRate: clk parent rate
 * @param  bestdiv: the returned bestdiv.
 * @return HAL_Status.
 */
HAL_Status
HAL_CRU_ClkNp5BestDiv (
  uint32_t  clockId,
  uint32_t  rate,
  uint32_t  pRate,
  uint32_t  *bestdiv
  )
{
  CRU_CLOCK  *clk;
  uint32_t   maxDiv;
  uint32_t   i;

  clk = HAL_CRU_ClkGetById (clockId);
  if (clk == NULL) {
    return HAL_ERROR;
  }

  maxDiv = CLK_DIV_GET_MASK (clk->div);

  for (i = 0; i < maxDiv; i++) {
    if (((pRate * 2) == (rate * (i * 2 + 3)))) {
      *bestdiv = i;

      return HAL_OK;
    }
  }

  return HAL_ERROR;
}

/**
 * @brief vop dclk enable.
 * @param  gateId: gate id
 * @return HAL_Status.
 * @attention these APIs allow direct use in the HAL layer.
 */
__WEAK HAL_Status
HAL_CRU_VopDclkEnable (
  uint32_t  gateId
  )
{
  HAL_CRU_ClkEnable (gateId);

  return HAL_OK;
}

/**
 * @brief vop dclk disable.
 * @param  gateId: gate id
 * @return HAL_Status.
 * @attention these APIs allow direct use in the HAL layer.
 */
__WEAK HAL_Status
HAL_CRU_VopDclkDisable (
  uint32_t  gateId
  )
{
  HAL_CRU_ClkDisable (gateId);

  return HAL_OK;
}

/**
 * @brief  assert CRU global software reset.
 * @param  type: global software reset type.
 * @return HAL_INVAL if the SoC does not support.
 */
HAL_Status
HAL_CRU_SetGlbSrst (
  eCRU_GlbSrstType  type
  )
{
 #ifdef CRU_GLB_SRST_FST_VALUE_OFFSET
  if (type == GLB_SRST_FST) {
    CRU->GLB_SRST_FST_VALUE = GLB_SRST_FST;
  }

 #endif
 #ifdef CRU_GLB_SRST_SND_VALUE_OFFSET
  if (type == GLB_SRST_SND) {
    CRU->GLB_SRST_SND_VALUE = GLB_SRST_SND;
  }

 #endif

  return HAL_INVAL;
}

/**
 * @brief  IP Clock is reset API
 * @param  clk: clk reset id
 * @return HAL_Check
 */
HAL_Check
HAL_CRU_RstIsAsserted (
  uint32_t  resetId
  )
{
  CRU_RESET  *rst;
  uint32_t   index, shift, address;
  HAL_Check  ret;

  rst = HAL_CRU_RstGetById (resetId);
  if (rst == NULL) {
    ASSERT (FALSE);
    return HAL_FALSE;
  }

  index = CLK_RESET_GET_REG_OFFSET (rst->srst);
  shift = CLK_RESET_GET_BITS_SHIFT (rst->srst);
  HAL_ASSERT (shift < 16);
  address = CRU_CON_REG_ADDRESS (rst->regBase, rst->srstOffset, index);

  ret = (HAL_Check)((MmioRead32 (address) & (1 << shift)) >> shift);

  return ret;
}

/**
 * @brief  IP Clock Reset Assert API
 * @param  clk: clk reset id
 * @return NONE
 */
HAL_Status
HAL_CRU_RstAssert (
  uint32_t  resetId
  )
{
  CRU_RESET  *rst;
  uint32_t   index, shift, address;

  rst = HAL_CRU_RstGetById (resetId);
  if (rst == NULL) {
    ASSERT (FALSE);
    return HAL_ERROR;
  }

  index = CLK_RESET_GET_REG_OFFSET (rst->srst);
  shift = CLK_RESET_GET_BITS_SHIFT (rst->srst);
  HAL_ASSERT (shift < 16);
  address = CRU_CON_REG_ADDRESS (rst->regBase, rst->srstOffset, index);

  MmioWrite32 (address, VAL_MASK_WE (1U << shift, 1U << shift));

  return HAL_OK;
}

/**
 * @brief  IP Clock Reset Deassert API
 * @param  clk: clk reset id
 * @return NONE
 */
HAL_Status
HAL_CRU_RstDeassert (
  uint32_t  resetId
  )
{
  CRU_RESET  *rst;
  uint32_t   index, shift, address;

  rst = HAL_CRU_RstGetById (resetId);
  if (rst == NULL) {
    ASSERT (FALSE);
    return HAL_ERROR;
  }

  index = CLK_RESET_GET_REG_OFFSET (rst->srst);
  shift = CLK_RESET_GET_BITS_SHIFT (rst->srst);
  HAL_ASSERT (shift < 16);
  address = CRU_CON_REG_ADDRESS (rst->regBase, rst->srstOffset, index);

  MmioWrite32 (address, VAL_MASK_WE (1U << shift, 0U << shift));

  return HAL_OK;
}

/** @} */

/** @} */

/** @} */
